例えばこのサイトだとIntersectionObserverを使っていて、これはスマホなどでまだ対応しておらず見れないことが多いので含めています。
なぜ Webpack の entry に含めたいのか
<script>での読み込みは Lighthouse のスコアを下げる
HTMLで以下の Polyfil を読み込むことで、IntersectionObserverを対応していないサイトでも使えるようにできます。
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>しかしこれは、Minimize Critical Requests Depthとして引っかかってしまいます。
NextJS の entry ファイルのルートで Polyfil を読み込む
上のnpm版である、intersection-observerを使います。
Polyfil は基本エントリーの最初に読み込むファイルの先頭にimportを記載すれば使えるので、_app.[jt]sx?をその形にすれば使えそうです。しかし、この Polyfil は中で window オブジェクトへアクセスしている部分があり NextJS ではその_appファイルも SSR で使われるので、 「windowがundefinedだよ」なエラーが起こってしまうのでこれも駄目です。
// _app.tsx
import 'intersection-observer';NextJS の Webpack-Config の entry に含める
next.config.jsからwebpack設定を自身の設定で上書きする方法です。
next.config.jsのデフォルトentryは() => Promise<{[x: string]: string[]}>な形をしていて、このオブジェクトを上書きすれば良さそうです。以下は戻り値の例です。
Promise {
{ 'main.js': [],
'static/runtime/main.js':
[ '/Users/nju33/github/blog/node_modules/next/dist/client/next-dev' ],
'static/development/pages/_app.js': [ './pages/_app.tsx' ],
'static/development/pages/_error.js':
[ '/Users/nju33/github/blog/node_modules/next/dist/pages/_error.js' ] } }このmain.jsへ含めてます。
{
webpack: config => {
const originalEntry = config.entry;
config.entry = () => {
return originalEntry().then(entry => {
entry['main.js'] = 'intersection-observer';
return entry;
}
}
}
}ただまだこれだと、サーバーサイド用のビルドファイルにもintersection-observer Polyfil が含まれてしまい、上記と同じエラーが起きてしまうので、含めるのは Client 用のビルドファイルだけにする必要があります。
幸いにも、next.config.jsのwebpack関数は第二引数にoptions.isServerというフラグを渡してくれます。これは Client 用のビルド時にはfalseになってくれるフラグです。
つまり、
options.isServerがtrueのときは、デフォルトのままのentryを返すfalseのときは、intersection-observerを含める
とすれば解決できそうです。config.entryを以下のように改良してみます。
{
webpack: (config, {isServer}) => {
const originalEntry = config.entry;
config.entry = () => {
if (isServer) {
return originalEntry;
}
return originalEntry().then(entry => {
entry['main.js'] = 'intersection-observer';
return entry;
}
}
}
}上のように変更したところ Lighthouse のスコアがあがり、かつIntersectionObserverに対応していない端末も対応することができました。
